/*
 * @features: 主要功能
 * @description: 内容说明
 * https://www.lodashjs.com/docs/lodash.fromPairs 
 * @Date: 2021-09-28 10:09:04
 * @Author: judu233(769471424@qq.com)
 * @LastEditTime: 2022-08-15 15:55:07
 * @LastEditors: zhangjiajun
 */

import SqlUtil from "../../Storage/SqlUtil";
import { ExtendsLoad } from "../CCExtends";

/**扩展原型组件 */
@ExtendsLoad(Array)
export class ArrayBaseExtends {
    /**
    * 复制二维数组
    * @param array 目标数组 
    */
    static copy2d(array: any[][]): any[][] {
        let newArray: any[][] = [];
        for (let i = 0; i < array.length; i++) {
            newArray.push(array[i].concat());
        }
        return newArray;
    }


    /**
     * 自动补全指定范围的数，并返回列表
     * @param start 开始数
     * @param end 结尾数
     * @param fillStartNum  按序填充时生效，按序填充时的开始值,默认开始从0开始填充
     * @param isFillSameNum  是否填充相同数字，默认不填充
     * @example 
     *  autoCompletion(1, 5) => [x,0,1,2,3,4] ;
     *  autoCompletion(0, 5) => [0,1,2,3,4,5] ;
     *  autoCompletion(1, 5, 1) => [x,1,2,3,4,5] ;
     *  autoCompletion(1, 5, 0, true) => [x,0,0,0,0,0] ;
     *  autoCompletion(1, 5, null, true) => [x,0,0,0,0,0] ;
     */
    static autoCompletion(start: number, end: number, fillStartNum = 0, isFillSameNum = false) {
        if (start > end) {
            cc.warn(`自动补全范围数的开始数不能比结尾数高`);
            return [start, end];
        }
        let list = [start];
        for (let i = 0; i < end - start; i++) {
            if (isFillSameNum) {
                list.push(fillStartNum);
            } else {
                list.push(start + i + fillStartNum)
            }
        }
        return list;
    }
}

/**扩展组件实例方法 */
@ExtendsLoad(Array.prototype)
export class ArrayExtends {
    /**
     * 存储当前数组里的元素
     * @param saveStr 要保存的key
     */
    static saveArr(saveStr: string) {
        let array = this as any as any[];
        return SqlUtil.setItem(saveStr, array);
    }

    /**
     * 从存储中加载指定字符的数据，并覆盖当前数组对应的元素
     * @param reloadStr 要加载存储的key
     * @param def 默认值
     * @param isClear 是否清理数组里的值，默认不清除
     * @returns 
     */
    static reloadArr(reloadStr: string, def = [], isClear = false) {
        try {
            let array = this as any as any[];
            let getArr = SqlUtil.getItem(reloadStr, def);
            if (isClear) {
                array.length = 0;
                array.push(...getArr);
            } else {
                for (let [name, elm] of Object.entries(getArr)) {
                    array[name] = elm;
                }
            }
            return true;
        } catch (error) {
            return false;
        }
    }

    /**
     * Fisher–Yates shuffle 随机置乱算法，返回被随机的元素
     * https://gaohaoyang.github.io/2016/10/16/shuffle-algorithm/
     * @param isChangeArr 是否改变原数组,( 默认否，否将不会改变原数组的顺序)
     * @example 
     */
    static fisherYatesShuffle<T>(isChangeArr = false) {
        let target = this as any as T[];
        let shuffle = (arr: T[]) => {
            for (let i = arr.length - 1; i >= 0; i--) {
                let randomIndex = Math.floor(Math.random() * (i + 1));
                [arr[randomIndex], arr[i]] = [arr[i], arr[randomIndex]];
            }
            return arr;
        }
        if (isChangeArr)
            return shuffle(target);
        else
            return shuffle([...target]);
    }

    /**
    * 简单随机混淆数组，返回被随机的元素
    * @param isChangeArr 是否改变原数组, 默认否，将不会改变原数组的顺序
    * @example 
    * [1,2,3].confound()  =>  [2,1,3];
    */
    static confound<T>(isChangeArr = false) {
        let array = this as any as T[];
        if (isChangeArr)
            return array.slice().sort(() => Math.random() - .5);
        else
            return [...array].slice().sort(() => Math.random() - .5);
    }

    /**
     * 随机返回数组中的一个元素，不修改原数组
     * @example 
     * [1,4,2,3].arrayRand()  =>  2
     */
    static arrayRand<T>(): T {
        let array = this as any as T[];
        if (array.length <= 0) {
            return null;
        }
        return array[Number.randInt(0, array.length)];
    }

    /**
    * 获取随机数组成员
    * @example 
    * [2,5,3,5].getRandomValueInArray() => 3
    */
    static getRandomValueInArray<T>(): T {
        let array = this as any as any[];
        let random = array[Math.floor(Math.random() * array.length)];
        return random;
    }

    /**
     * 数组多层扁平化
     * @example 
     * [1,[2]].flattening()  =>  [1,2];
     * [2, [33] , [3,[3]] ].flattening()  =>  [2, 33, 3, 3]
     */
    static flattening<T>(): T[] {
        let array = this as any as T[];
        while (array.some(v => Array.isArray(v))) {
            array = [].concat.apply([], array);
        }
        return array;
    }

    /**
    * 合并指定目标的数组并返回，不修改原数组
    * @param array2 目标数组2
    * @example 
    * [1,2].combineArr([3,4]) => [1,2,3,4];
    */
    static combineArr<T>(array2: T[]) {
        let array = this as any as T[];
        let newArray = [...array, ...array2];
        return newArray;
    }

    /**
     * 交换数组中指定目标序列的元素（节点会交换层级）
     * @param first 要交换的index
     * @param change 要交换的index
     * @param isChangeIndex  交换时改变层级（默认改变层级）
     * @example 
     *  [node1, node2].exchange(0,1)  ->  [node2,node1] （同时层级也会发生变换）
     */
    static exchangeNode(first: number, change: number, isChangeIndex = true) {
        let array = this as any as cc.Node[];
        if (first >= 0 && first < array.length && change >= 0 && change < array.length) {
            let o = array[first];
            let t = array[change];
            if (o != null && t != null) {
                [array[change], array[first]] = [o, t];
                console.log(`交换顺序，原顺序:${first}，交换后的顺序:${change}`);

                //同层交换层级
                if (isChangeIndex) {
                    o?.setSiblingIndex(change);
                    t?.setSiblingIndex(first);
                }
            } else
                console.warn('元素为空，交换失败' + array, first, change);
        } else
            console.warn('交换失败' + array, first, change);
    }

    /**
    * 检查列表元素是否有null， 如果有将返回true
    * @returns boolen
    * @example
    *   [...].hasNull()  =>  bool
    */
    static hasNull() {
        let array = this as any as any[];
        array.map(v => console.log(v));
        for (let value of array)
            if (value == null)
                return true;
        return false;
    }

    /**
    * 检查列表元素是否有undefine， 如果有将返回true
    * @returns boolen
    *   [...].hasUndf()  =>  bool
    */
    static hasUndf() {
        let array = this as any as any[];
        for (let value of array)
            if (value == undefined)
                return true;
        return false;
    }

    /**
     * 根据下标交换数组两个元素位置
     * @param idx1 index1
     * @param idx2 index2
     * @example 
     *  [1,2,3].arraySwap(0,2) => [3,2,1]
     */
    static arraySwap<T>(idx1: number, idx2: number) {
        let array = this as any as T[];
        if (idx1 === idx2 || !Number.inRange(0, array.length - 1, idx1) || !Number.inRange(0, array.length - 1, idx2)) {
            return;
        }
        [array[idx1], array[idx2]] = [array[idx2], array[idx1]];
    }

    /**
    * 判断数组中是否有某个元素
     * @param ele 元素
    */
    static arrayHas<T>(ele: T) {
        let array = this as any as T[];
        let idx = array.findIndex((e) => { return e === ele; });
        return idx >= 0;
    }

    /**
     * 将元素从fromIdx位置移到toIdx位置，其余元素相对位置不变
     * @param fromIdx index1
     * @param toIdx index2
     */
    static arrayMove<T>(fromIdx: number, toIdx: number) {
        let array = this as any as T[];
        if (fromIdx === toIdx || !Number.inRange(0, array.length - 1, fromIdx) || !Number.inRange(0, array.length - 1, toIdx)) {
            return;
        }
        let from: T[] = array.splice(fromIdx, 1);
        array.splice(toIdx, 0, from[0]);
    }

    /**
     * 在数组中添加某个元素
     * @param ele 元素
     * @param canRepeat 是否可重复添加相同元素 默认false
     * @return 是否执行了添加行为
     */
    static arrayAdd<T>(ele: T, canRepeat = false) {
        let array = this as any as T[];
        if (!canRepeat && array.arrayHas(ele)) {
            return false;
        }
        array.push(ele);
        return true;
    }

    /**
     * 在数组中删除某个元素(若有多个相同元素则只删除第一个)
     * @param ele 元素
     * @return 是否执行了删除行为
     */
    static arrayDelete<T>(ele: T) {
        let array = this as any as T[];
        let index: number = array.findIndex((e) => { return e === ele; });
        if (index >= 0) {
            array.splice(index, 1);
            return true;
        } else {
            return false;
        }
    }

    /**
     * 从数组中取出指定数量的元素
     * @param count 数量
     * @returns elm
     */
    static getElmCount<T>(count: number) {
        let array = this as any as T[];
        return array.slice(0, count);
    }

    /**
     * 把当前的数组转换成map结构返回
     * @returns map
     */
    static transMap() {
        let array = this as any as any[];
        return new Map(array);
    }

    /**
     * 把当前的数组转换成set结构返回
     * @returns set
     */
    static transSet() {
        let array = this as any as any[];
        return new Set(array);
    }

    /**
     * 根据指定数组覆盖当前数组
     * @param target 目标数组
     */
    static overrideArr(target: []) {
        let array = this as any as any[];
        for (let i of Object.keys(target)) {
            array[i] = target[i];
        }
    }

    /**
     * 获取数组的最后一个元素，不改变数组
     * @returns any
     */
    static last() {
        let array = this as any as any[];
        return array[array.length - 1];
    }

    /**
    * 获取数组的第一个元素，不改变数组
    * @returns any
    */
    static first() {
        let array = this as any as any[];
        return array[0];
    }
 
    /**
     * 排序
     * @param arr 数组
     * @param attr 属性
     * @param asc 是否倒序
     * @param ignore 
     * @returns 
     */
    static selObjArr(arr, attr, asc, ignore) {
        let getCompFn = (asc) => {
            let sortFn;
            if (typeof asc === 'function') {
                sortFn = asc;
            } else {
                if (asc === false) {
                    sortFn = function (a, b) {
                        if (typeof a === 'string') {
                            return b.localeCompare(a)
                        }
                        if (typeof a === 'number') {
                            return b - a
                        }
                        return 0
                    }
                } else {
                    sortFn = function (a, b) {
                        if (typeof a === 'string') {
                            return a.localeCompare(b)
                        }
                        if (typeof a === 'number') {
                            return a - b
                        }
                        return 0
                    }
                }
            }
            return sortFn
        }
        let sortFn = getCompFn(asc);
        if (attr === undefined || arr.length === 0) {
            return [
                [],
                []
            ];
        }
        if (arr.length === 1) {
            return [arr.concat(), []];
        }
        if (!ignore) {
            arr.sort(function (a: object, b: object) {
                return sortFn(a.getObjectValue(attr), b.getObjectValue(attr))
            });
        }
        // 首先，选择属性匹配的差异数组
        let diffIndex = 0;
        for (let i = 0, len = arr.length - 1; i < len; i++) {
            if (!sortFn(arr[i].getObjectValue(attr), arr[i + 1].getObjectValue(attr))) {
                diffIndex = i;
                break
            }
            if (i == len - 1) {
                diffIndex = len + 1;
                break
            }
        }
        let diffArr = arr.slice(0, diffIndex);

        // then, select same arrays whose attribute matches
        let sameIndex = 0;
        let leftArr = arr.slice(diffIndex);
        for (let j = 0, len2 = leftArr.length - 1; j < len2; j++) {
            if (sortFn(leftArr[j].getObjectValue(attr), leftArr[j + 1].getObjectValue(attr))) {
                sameIndex = j;
                break
            }
            if (j == len2 - 1) {
                sameIndex = len2;
                break
            }
        }
        let sameArr = leftArr.slice(0, sameIndex + 1);
        return [diffArr, sameArr]
    }

    /**
     * 数组多条件排序算法
     * @param arr 要排序的数组
     * @param sortList 排序规则
     * @returns 
     * @example
     *  1.  var arr = [{ foo: 'y' }, { foo: 'z' }, { foo: 'x' }];
            arrSort(arr, [{ attr: 'foo' }]);//=> [{foo: 'x'}, {foo: 'y'}, {foo: 'z'}]
        2.  var arr = [{ foo: 'y' }, { foo: 'z' }, { foo: 'x' }];
            arrSort(arr, [{ attr: 'foo', asc: false }]);//=> [{foo: 'z'}, {foo: 'y'}, {foo: 'x'}]
        3.  var array = [
                { foo: 'bbb', num: 4, flag: 2 },
                { foo: 'aaa', num: 3, flag: 1 },
                { foo: 'ccc', num: -6, flag: 2 },
                { foo: 'ccc', num: 8, flag: 2 },
                { foo: 'bbb', num: 2, flag: 4 },
                { foo: 'aaa', num: -3, flag: 4 }
            ];
            // sort by `flag`, then `foo`, then `num`
            var result = arrSort(array,
                [{   attr: 'flag',   asc: true },
                {    attr: 'foo',    asc: false },
                {    attr: 'num',    asc: true}]
            );
            =>  // [ { foo: 'aaa', num: 3,  flag: 1},
                //   { foo: 'ccc', num: -6, flag: 2},
                //   { foo: 'ccc', num: 8,  flag: 2},
                //   { foo: 'bbb', num: 4,  flag: 2},
                //   { foo: 'bbb', num: 2,  flag: 4},
                //   { foo: 'aaa', num: -3, flag: 4} ]
        4.  var array = [
                { locals: { foo: 'bbb', num: 4 }, flag: 2 },
                { locals: { foo: 'aaa', num: 3 }, flag: 1 },
                { locals: { foo: 'ccc', num: -6 }, flag: 2 },
                { locals: { foo: 'ccc', num: 8 }, flag: 2 },
                { locals: { foo: 'bbb', num: 2 }, flag: 4 },
                { locals: { foo: 'aaa', num: -3 }, flag: 4 },
            ];
            // sort by `flag`, then `locals.foo`, then `locals.num`
            var result = arrSort(array,
                [{   attr: 'flag',        asc: true},
                {    attr: 'locals.foo',  asc: false},
                {    attr: 'locals.num',  asc: true }]
            );
            console.log(result);
            // [ { locals: { foo: 'aaa', num: 3 },  flag: 1},
            //   { locals: { foo: 'ccc', num: -6 }, flag: 2},
            //   { locals: { foo: 'ccc', num: 8 },  flag: 2},
            //   { locals: { foo: 'bbb', num: 4 },  flag: 2},
            //   { locals: { foo: 'bbb', num: 2 },  flag: 4},
            //   { locals: { foo: 'aaa', num: -3 }, flag: 4} ]
        5.  var array = [
                { locals: { foo: 'bbb', num: 4 }, flag: -2 },
                { locals: { foo: 'aaa', num: 3 }, flag: 1 },
                { locals: { foo: 'ccc', num: -6 }, flag: 2 },
                { locals: { foo: 'ccc', num: 8 }, flag: 2 },
                { locals: { foo: 'bbb', num: 2 }, flag: 4 },
                { locals: { foo: 'aaa', num: -3 }, flag: 4 },
            ];
            // sort by `flag`, then `locals.foo`, then `locals.num`
            var result = arrSort(array,
                [{   attr: 'flag',         asc: function (a, b) { return (Math.abs(a) - Math.abs(b)) }  },
                {    attr: 'locals.foo',   asc: false },
                {    attr: 'locals.num',   asc: true}]
            );
            console.log(result);
            // [ { locals: { foo: 'aaa', num: 3 },  flag: 1},
            //   { locals: { foo: 'ccc', num: -6 }, flag: 2},
            //   { locals: { foo: 'ccc', num: 8 },  flag: 2},
            //   { locals: { foo: 'bbb', num: 4 },  flag: -2},
            //   { locals: { foo: 'bbb', num: 2 },  flag: 4},
            //   { locals: { foo: 'aaa', num: -3 }, flag: 4} ]
     */
    static arrSort(arr, sortList) {
        // check params
        if (arr == null) {
            return [];
        } else if (Object.prototype.toString.call(arr) !== "[object Array]") {
            throw new TypeError('array param MUST BE ARRAY');
        }

        if (sortList == null) {
            return arr;
        } else if (Object.prototype.toString.call(sortList) !== "[object Array]") {
            throw new TypeError('comparisonArgs param MUST BE ARRAY');
        }

        let i = 0;
        let len = sortList.length;
        let inArr = [];
        let outArr = [];
        let isSorted = false

        if (!len) {
            return arr;
        }

        arr.forEach((item, index) => {
            if (Object.prototype.toString.call(item) !== "[object Object]") {
                throw new TypeError('the item of array param MUST BE OBJECT');
            }
            // mark array item for follow-up deleting
            item.$$index = index
            inArr.push(item)
        });

        sortList.forEach(item => {
            if (Object.prototype.toString.call(item) !== "[object Object]") {
                throw new TypeError('the item of comparisonArgs param MUST BE OBJECT');
            }
        });

        function sortInner(arr, sortObj) {
            if (arr.length === 0) {
                return;
            }
            // mark first attr sorted, optimize performance
            let ignore = sortObj.attr === sortList[0].attr && isSorted
            let filterArr = this.selObjArr(arr, sortObj.attr || '', sortObj.asc, ignore);
            if (!isSorted) {
                isSorted = true
            }

            // if pick out matched arrays, push into outArr
            if (filterArr[0].length || i >= len - 1) {
                let rsArr = filterArr[0].concat(i >= len - 1 ? filterArr[1] : [])
                Array.prototype.push.apply(outArr, rsArr);
                // delete the corresponding array, update inArr
                let newInArr = []
                let filterIndexArr = []
                for (let k = 0, len1 = rsArr.length; k < len1; k++) {
                    filterIndexArr.push(rsArr[k].$$index)
                }
                for (let j = 0, len2 = inArr.length; j < len2; j++) {
                    if (filterIndexArr.indexOf(inArr[j].$$index) === -1) {
                        newInArr.push(inArr[j])
                    }
                }
                inArr = newInArr
                // free the memory
                rsArr = null
                newInArr = null
                filterIndexArr = null
            }

            // if there is left arrays, next turn
            if (filterArr[1].length && i < len - 1) {
                i++;
                sortInner(filterArr[1], sortList[i])
            }
        }

        function sortOuter() {
            i = 0;
            sortInner(inArr, sortList[0]);
            if (inArr.length === 0 || arr.length === outArr.length) {
                return
            }
            sortOuter();
        }

        sortOuter();
        outArr.forEach(item => {
            delete item.$$index
        });

        return outArr
    }
}
